/**
 * Title: Hyperbolic engine
 * Description: jFamilyTree Engine
 * Version: provider
 * Copyright: (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 * Update: Mon Mar 10 00:00:00 CEST 2008
 */
package jFamilyTree.core;

import jFamilyTree.model.INode;
import jFamilyTree.model.Settings;

/**
 * Layout agent
 * 
 * @author Bernard Bou
 */
public abstract class AbstractLayerOut
{
	// D A T A

	// default values
	
	/**
	 * Default orientation
	 */
	static protected final Complex theDefaultOrientation = Complex.ZERO;

	/**
	 * Default expansion
	 */
	static protected final double theDefaultExpansion = .3;

	/**
	 * Default root radial sweep
	 */
	static protected final double theDefaultRadialRootSweep = Math.PI;

	/**
	 * Default oriented (non-radial) root sweep
	 */
	static private final double theDefaultOrientedRootSweep = Math.PI / 2.;

	/**
	 * Default radial child sweep
	 */
	static private final double theDefaultRadialChildSweep = Math.PI / 2.;

	/**
	 * Default oriented (non-radial) child sweep
	 */
	static private final double theDefaultOrientedChildSweep = Math.PI / 2.;

	// settings
	
	/**
	 * Root orientation
	 */
	protected Complex theRootOrientation;

	/**
	 * Root sweep
	 */
	protected double theRootSweep;

	/**
	 * Node distance
	 */
	protected double theNodeDistance;

	/**
	 * Sweep factor
	 */
	protected double theSweepFactor;

	/**
	 * Radius
	 */
	protected double theRadius;

	/**
	 * Node sweep
	 */
	protected double theNodeSweep;

	/**
	 * Whether node space is allocated clockwise
	 */
	protected boolean clockwise;

	// C O N S T R U C T O R

	/**
	 * Contructor
	 */
	public AbstractLayerOut()
	{
		// default radial
		boolean isRadial = setDefaultOrientation();
		setDefaultExpansion();
		setDefaultRootSweep(isRadial);
		setDefaultChildSweep(isRadial);
		clockwise = false;
	}

	// O P E R A T I O N

	/**
	 * Do layout
	 */
	abstract public void layout(INode thisNode);

	/**
	 * Do layout
	 * 
	 * @param thisNode
	 *        start node
	 * @param thisCenter
	 *        starting point (hyperbolic circle center)
	 * @param thisHalfWedge
	 *        half wedge allocated to layout
	 * @param thisOrientation
	 *        orientation
	 */
	abstract public void layout(INode thisNode, Complex thisCenter, double thisHalfWedge, double thisOrientation);

	// A C C E S S

	/**
	 * Get layout orientation
	 * 
	 * @return orientation
	 */
	public Complex getOrientation()
	{
		return theRootOrientation;
	}

	/**
	 * Set layout orientation
	 * 
	 * @param thisOrientation
	 */
	public void setOrientation(Complex thisOrientation)
	{
		theRootOrientation = thisOrientation;
		clockwise = !(thisOrientation == Complex.NORTH || thisOrientation == Complex.EAST);
	}

	/**
	 * Get expansion factor
	 * 
	 * @return expansion factor
	 */
	public double getExpansion()
	{
		return theNodeDistance;
	}

	/**
	 * Set expansion factor
	 * 
	 * @param thisExpansion
	 *        expansion factor
	 */
	public void setExpansion(double thisExpansion)
	{
		theNodeDistance = thisExpansion;
		theRadius = XMath.e2h(theNodeDistance);
	}

	/**
	 * Set root sweep angle allocated to layout
	 * 
	 * @return sweep angle allocated to layout
	 */
	public double getRootSweep()
	{
		return theRootSweep;
	}

	/**
	 * Get root sweep angle allocated to layout
	 * 
	 * @param thisSweep
	 *        sweep angle allocated to layout
	 */
	public void setRootSweep(double thisSweep)
	{
		theRootSweep = thisSweep;
	}

	/**
	 * Get sweep angle allocated to children
	 * 
	 * @return sweep angle allocated to children
	 */
	public double getChildSweep()
	{
		return theNodeSweep;
	}

	/**
	 * Set sweep angle allocated to children
	 * 
	 * @param thisSweep
	 *        sweep angle allocated to children
	 */
	public void setChildSweep(double thisSweep)
	{
		theNodeSweep = thisSweep;
		theSweepFactor = Math.PI - theNodeSweep;
	}
	
	/**
	 * Set root sweep to default
	 * 
	 * @param radial
	 *        true if layout is radial
	 */
	public void setDefaultRootSweep(boolean radial)
	{
		setRootSweep(radial ? theDefaultRadialRootSweep : theDefaultOrientedRootSweep);
	}

	/**
	 * Set child sweep to default
	 * 
	 * @param radial
	 *        true if layout is radial
	 */
	public void setDefaultChildSweep(boolean radial)
	{
		setChildSweep(radial ? theDefaultRadialChildSweep : theDefaultOrientedChildSweep);
	}

	// H E L P E R S

	/**
	 * Set orientation to default
	 * 
	 * @return true if default orientation is radial
	 */
	private boolean setDefaultOrientation()
	{
		setOrientation(theDefaultOrientation);
		return theDefaultOrientation == Complex.ZERO;
	}

	/**
	 * Set expansion to default
	 */
	private void setDefaultExpansion()
	{
		setExpansion(theDefaultExpansion);
	}

	/**
	 * Apply settings
	 * 
	 * @param theseSettings
	 *        settings
	 */
	public void apply(Settings theseSettings)
	{
		boolean radial = true;
		if (theseSettings.theOrientation != null)
		{
			if (theseSettings.theOrientation.startsWith("n"))
			{
				setOrientation(Complex.SOUTH);
				radial = false;
			}
			else if (theseSettings.theOrientation.startsWith("s"))
			{
				setOrientation(Complex.NORTH);
				radial = false;
			}
			else if (theseSettings.theOrientation.startsWith("e"))
			{
				setOrientation(Complex.EAST);
				radial = false;
			}
			else if (theseSettings.theOrientation.startsWith("w"))
			{
				setOrientation(Complex.WEST);
				radial = false;
			}
			else if (theseSettings.theOrientation.startsWith("r"))
			{
				setOrientation(Complex.ZERO);
				radial = true;
			}
		}

		// expansion
		setDefaultExpansion();
		if (theseSettings.theExpansion != null && theseSettings.theExpansion > 0.)
			setExpansion(getExpansion() * theseSettings.theExpansion);

		// sweeps
		setDefaultRootSweep(radial);
		setDefaultChildSweep(radial);
		if (theseSettings.theSweep != null && theseSettings.theSweep > 0.)
			setChildSweep(getChildSweep() * theseSettings.theSweep);
	}
}
